package question.interview.cacheLine;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.junit.Test;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.function.*;

public class Q1 {

    /**
     * 间隔的访问数据，发现间隔为 2，4，8，16 消耗时间基本相差不大,因为Cache line缓存大小为64B 等于16个int类型的长度,所以访问次数增加实际消耗时间不大。32，64，128因为每次访问都是直接从内存中加载到cache line中，实际没有时间缓存，所以消耗时间与访问次数正相关。
     * 间隔 2 消耗时间：352
     * 间隔 4 消耗时间：246
     * 间隔 8 消耗时间：272
     * 间隔 16 消耗时间：251
     * 间隔 32 消耗时间：175
     * 间隔 64 消耗时间：115
     * 间隔 128 消耗时间：64
     */
    @Test
    public void test01() {
        int[] nums = new int[10 * 64 * 1024 * 1024];

        Consumer<Integer> countSize = step -> {
            for (int i = 0; i < nums.length; i += step) {
                nums[i] = i;
            }
        };

        BiConsumer<Consumer<Integer>, Integer> printTime = (c, p) -> {
            Long startTime = System.currentTimeMillis();
            c.accept(p);
            System.out.println("间隔 " + p + " 消耗时间：" + (System.currentTimeMillis() - startTime));
        };
        printTime.accept(countSize, 2);
        printTime.accept(countSize, 4);
        printTime.accept(countSize, 8);
        printTime.accept(countSize, 16);
        printTime.accept(countSize, 32);
        printTime.accept(countSize, 64);
        printTime.accept(countSize, 128);

    }

    /**
     * 二维数组 列循环消耗时间比行循环大。
     * 因为列遍历无法使用缓存导致
     */
    @Test
    public void test02() {
        int nums[][] = new int[16 * 1024][16 * 1024];
        Supplier c1 = () -> {
            for (int i = 0; i < nums.length; i++) {
                for (int j = 0; j < nums[0].length; j++) {
                    nums[i][j] = 0;
                }
            }
            return 1;
        };

        Supplier c2 = () -> {
            for (int i = 0; i < nums.length; i++) {
                for (int j = 0; j < nums[0].length; j++) {
                    nums[j][i] = 0;
                }
            }
            return 1;
        };


        System.out.println("行遍历");
        printCountTime(c1);
        System.out.println("列遍历");
        printCountTime(c2);

    }

    private Object printCountTime(Supplier s) {
        Long startTime = System.currentTimeMillis();
        Object o = s.get();
        long resultCostTime = System.currentTimeMillis() - startTime;
        System.out.println(" 消耗时间：" + resultCostTime);
        return o;
    }

    /**
     * 多线程统计数组中所有数字的和,发现如果在每个线程中直接操作result数组的话，会导致缓存失效，进而导致性能下降。
     */
    @Test
    public void test03() throws InterruptedException {
        int[] nums = new int[8*64 * 1024 * 1024];
        int threadNums = Runtime.getRuntime().availableProcessors();
        long[] result = new long[threadNums];


        Random random = new Random();
        for (int i = 0 ;i<nums.length ;i++) {
            nums[i] = random.nextInt(1000);
        }

        Supplier s = () -> {
            long singleThreadResult = 0;
            for (int num : nums) {
                singleThreadResult += num;
            }
            return singleThreadResult;
        };

        System.out.println("直接使用单线程数组遍历");
        Object o = printCountTime(s);
        System.out.println(o);
        System.out.println("---------------------");

        //直接使用数组去相加
        Consumer<Integer> c1 = (threadNum)->{
            int eachSize = nums.length / threadNums;
            int start = threadNum*eachSize;
            int end  = (start+eachSize)>nums.length?nums.length:(start+eachSize);
            for (int i =start ;i<end;i++){
                result[threadNum] += nums[i];
            }
        };



        //使用临时变量相加
        Consumer<Integer> c2 = (threadNum)->{
            int eachSize = nums.length / threadNums;
            int start = threadNum*eachSize;
            int end  = (start+eachSize)>nums.length?nums.length:(start+eachSize);
            long sss = 0L ;
            for (int i =start ;i<end;i++){
                sss+= nums[i];
            }
            result[threadNum] = sss;
        };



        CountDownLatch count = new CountDownLatch(threadNums);
        Long startTime = System.currentTimeMillis();
        for(int i = 0 ;i<threadNums;i++){
            new Thread(new CountSum(count,c1,i)).start();
        }
        count.await();
        System.out.println("直接使用result，导致缓存失效消耗时间"+(System.currentTimeMillis()-startTime));
        long sum = 0;
        for (long l : result) {
            sum+=l;
        }
        System.out.println(sum);
        System.out.println("----------------------");


        count = new CountDownLatch(threadNums);
        startTime = System.currentTimeMillis();
        for(int i = 0 ;i<threadNums;i++){
            new Thread(new CountSum(count,c2,i)).start();
        }
        count.await();
        System.out.println("直接使用result，导致缓存失效消耗时间"+(System.currentTimeMillis()-startTime));
        sum = 0;
        for (long l : result) {
            sum+=l;
        }
        System.out.println(sum);
        System.out.println("----------------------");

    }

    /**
     * 用来计算总和
     */
    @AllArgsConstructor
    @NoArgsConstructor
    class CountSum implements Runnable{

        @Setter
        CountDownLatch cc;
        @Setter
        Consumer c ;
        @Setter
        int threadNum;
        @Override
        public void run() {
            c.accept(threadNum);
            cc.countDown();
        }
    }

}
